package edu.isistan.matching.filters;

import java.io.ByteArrayOutputStream;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import edu.isistan.matching.filters.io.MatchingMap;
import edu.isistan.matching.filters.io.XMLMatchingRepresentation;
import edu.isistan.matching.scoreTypes.ComposeMatchScore;
import edu.isistan.matching.scoreTypes.InterfaceMatchScore;
import edu.isistan.matching.scoreTypes.MatchScore;
import edu.isistan.matching.scoreTypes.MessageMatchScore;
import edu.isistan.matching.scoreTypes.OperationMatchScore;
import edu.isistan.matching.scoreTypes.PrimitiveTypeMatchScore;
import edu.isistan.matching.scoreTypes.ScoreMatrix;
import edu.isistan.matching.structures.elements.MessageElement;
import edu.isistan.matching.structures.elements.PartElement;
import edu.isistan.matching.structures.elements.WSDLElement;
import edu.isistan.matching.structures.elements.types.PrimitiveType;
import edu.isistan.metamodel.DataType;
import edu.isistan.metamodel.ModelElement;

/**
 * Filtro encargado de generar una representación que represente los mapeos
 * entre dos servicios
 * 
 */
public class GenerateRepresentationFilter implements
		IMatchingFilter<MatchingMap, XMLMatchingRepresentation> {

	private static final String TARGET_PREFIX = "";

	private static final String SOURCE_PREFIX = "";

	private static final String TABS = "";

	Element rootXML;

	String source = null;
	String target = null;

	String sourceRef = null;
	String targetRef = null;

	static String TYPE_SEPARATOR = ".";

	List<PartElement> partsSourceList;
	List<PartElement> partsTargetList;
	Hashtable<String, Element> partCreados = new Hashtable<String, Element>();

	static Logger logger = Logger.getLogger(GenerateRepresentationFilter.class);

	public GenerateRepresentationFilter() {
		super();
	}

	public void initialize(String wsdlSource, String wsdlTarget,
			String sourceRef, String targetRef) {
		this.source = wsdlSource;
		this.target = wsdlTarget;
		this.sourceRef = sourceRef;
		this.targetRef = targetRef;
	}

	public XMLMatchingRepresentation process(MatchingMap parameter) {
		XMLMatchingRepresentation resultadoXML = new XMLMatchingRepresentation();
		InterfaceMatchScore resultado = (InterfaceMatchScore) parameter;
		rootXML = new Element("definition");
		generateSchema(rootXML);
		Element mappingXML = new Element("mapping");
		rootXML.addContent(mappingXML);

		ModelElement[][] mejoresOperaciones = resultado.getChildScoreMatrix().getMappingElements();
		MatchScore[] bestResult = resultado.getChildScoreMatrix().getBestMatchScore();
		for (int i = 0; i < mejoresOperaciones.length; i++) {
			ModelElement source = mejoresOperaciones[i][0];
			ModelElement target = mejoresOperaciones[i][1];
			getLogger().debug("operation Mapping Nro:" + (i + 1) + " Score:" + bestResult[i].getScore() + ": source: " + source.getName() + " target: "+ target.getName());
			OperationMatchScore matchScoreOp = (OperationMatchScore) bestResult[i];
			MatchScore inMatchScore = matchScoreOp.getMessageInMatchScore();
			MatchScore outMatchScore = matchScoreOp.getMessageOutMatchScore();

			Element operacionTag = new Element("operationMapping");
			operacionTag.setAttribute("source", source.getName());
			operacionTag.setAttribute("target", target.getName());
			operacionTag.setAttribute("matching-score", String.valueOf(matchScoreOp.getScore()));
			mappingXML.addContent(operacionTag);

			analizarMensaje("inMessageMapping",	(MessageMatchScore) inMatchScore, operacionTag);
			analizarMensaje("outMessageMapping", (MessageMatchScore) outMatchScore, operacionTag);
		}

		Format xmlFormat = Format.getPrettyFormat();
		XMLOutputter XMLOutPutter = new XMLOutputter(xmlFormat);
		ByteArrayOutputStream outputStream = null;
		try {
			outputStream = new ByteArrayOutputStream();
			XMLOutPutter.output(new Document(rootXML), outputStream);
			outputStream.flush();
			resultadoXML.setXml(outputStream.toByteArray());
		} catch (Exception e) {
			getLogger().error("Error al generar XML" + e.getMessage());
		} finally {
			try {
				outputStream.close();
			} catch (Exception e) {
			}
		}
		return resultadoXML;
	}

	private void generateSchema(Element rootXML) {
		Element schema = new Element("schema");
		Element source = new Element("source");
		source.setAttribute("path", this.source);
		if (this.sourceRef != null) {
			source.setAttribute("ref", this.sourceRef);
		}
		Element target = new Element("target");
		target.setAttribute("path", this.target);
		if (this.targetRef != null) {
			target.setAttribute("ref", this.targetRef);
		}
		schema.addContent(source);
		schema.addContent(target);
		rootXML.addContent(schema);
	}

	protected String getMessageName(MessageElement m) {
		if (m != null && m.getName().trim().length() > 0)
			return m.getName();
		else
			return "void";
	}

	public void analizarMensaje(String tipoMje, MessageMatchScore mjeMatchScore, Element parentElementXml) {
		Element mjeElement = new Element(tipoMje);
		parentElementXml.addContent(mjeElement);
		mjeElement.setAttribute("matching-score", String.valueOf(mjeMatchScore.getScore()));
		analizarTypes(mjeMatchScore, SOURCE_PREFIX, TARGET_PREFIX, mjeElement, TABS);
	}

	/**
	 * Busca el atributo <b>name</b> de un part (obtenido de la lista pasada por
	 * parámetro)cuyo atributo <b>type</b> sea igual al atributo <b>nameType</b>
	 * 
	 * @param nameType
	 * @param parts
	 *            Lista de parts de un mensaje
	 * @return nombre del part
	 */
	public String buscarPart(String nameType, List<PartElement> parts) {
		if ("".equals(nameType)) {
			return "";
		}
		for (Iterator iterator = parts.iterator(); iterator.hasNext();) {
			PartElement partT = (PartElement) iterator.next();
			if (partT.getType().getName().equals(nameType)) {
				return partT.getName();
			}
		}
		getLogger().debug(
				"No se ha encontrado un Part cuyo atributo Type sea igual a "
						+ nameType);
		return "";
	}

	public String buscarPartByPrimitiveType(String primitiveType,
			List<PartElement> parts) {
		if (primitiveType == null || "".equals(primitiveType)) {
			return "";
		}
		for (Iterator iterator = parts.iterator(); iterator.hasNext();) {
			PartElement partT = (PartElement) iterator.next();
			if (partT.getType().isPrimitivo()) {
				PrimitiveType primitive = (PrimitiveType) partT.getType();
				if (primitiveType.equals(primitive.getType())) {
					return partT.getName();
				}
			}
		}
		getLogger().debug(
				"No se ha encontrado un Part cuyo atributo Type sea igual a "
						+ primitiveType);
		return "";
	}

	/**
	 * 
	 * @param matchScore
	 *            MatchScore a analizar
	 * @param partNameSource
	 *            nombre del Part del Source (que engloba al tipo analizado)
	 * @param partNameTarget
	 *            nombre del Part del target (que engloba al tipo analizado)
	 * @param prefijoSource
	 *            concatenacion de nombres de tipos del source, separados por
	 *            TYPE_SEPARATOR
	 * @param prefijoTarget
	 *            concatenacion de nombres de tipos del target
	 * @param parentElement
	 *            Elemento de Jdom el cual será padre de aquellos creados por
	 *            este método.
	 * @param tabs
	 */
	public void analizarTypes(MatchScore matchScore, String prefijoSource, String prefijoTarget, Element parentElement, String tabs) {

		if (matchScore == null) {
			getLogger().error("Matriz Score = null");
			return;

		}

		if (matchScore.isComposeMatchScore()) {
			getLogger().debug(tabs + "Es complex:" + matchScore);
			ComposeMatchScore composeMatchScore = (ComposeMatchScore) matchScore;
			ScoreMatrix matrixScore = composeMatchScore.getChildScoreMatrix();

			ModelElement[][] mejoresElements = matrixScore.getMappingElements();
			MatchScore[] bestResult = matrixScore.getBestMatchScore();
			for (int i = 0; i < mejoresElements.length; i++) {
				DataType source = (DataType) mejoresElements[i][0];
				DataType target = (DataType) mejoresElements[i][1];
				if (source.getName() != null && target.getName() != null) {
					getLogger().debug(tabs + bestResult[i].getScore() + "  " + source.getName() + " <--> " + target.getName());
				}
				analizarTypes(bestResult[i], SOURCE_PREFIX, TARGET_PREFIX, parentElement, tabs + " \t ");
			}
		} else {
			if (matchScore instanceof PrimitiveTypeMatchScore) {
				PrimitiveTypeMatchScore primitive = (PrimitiveTypeMatchScore) matchScore;
				getLogger().debug(tabs + "Es primitivo:" + matchScore);
				Element typeMappingElement = new Element("typeMapping");

				DataType source = primitive.getType1();
				DataType target = primitive.getType2();

				typeMappingElement.setAttribute("sourceName", source.getName());
				typeMappingElement.setAttribute("sourceType", source.getPrettyPrintType());
				
				typeMappingElement.setAttribute("targetName", target.getName());
				typeMappingElement.setAttribute("targetType", target.getPrettyPrintType());

				typeMappingElement.setAttribute("matching-score", String.valueOf(primitive.getScore()));

				parentElement.addContent(typeMappingElement);
			}
		}
	}

	/**
	 * Retorna el atributo Name del Part, en caso de que el elemento sea del
	 * tipo PartElement
	 * 
	 * @param element
	 *            Elemento a analizar
	 * @return nombre del part
	 */
	private String getPartName(WSDLElement element) {
		if (element != null && element instanceof PartElement) {
			PartElement part = (PartElement) element;
			return part.getName();
		}

		return "";
	}

	public static Logger getLogger() {
		return logger;
	}
}
